﻿// <copyright file="BestSellerHistory.cs" company="Microsoft Corporation">
// Copyright (c) 2008 All Right Reserved
// </copyright>
// <author>Michael S. Scherotter</author>
// <email>mischero@microsoft.com</email>
// <date>2009-03-03</date>
// <summary>Best Seller History CLR Object</summary>

namespace NYTimes
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Windows;
    using System.Xml.Linq;
    using NYTimes.Core;
    using NYTimes.Data;
    using Synergist;

    /// <summary>
    /// Bestseller history sort order
    /// </summary>
    public enum BestsellerHistorySort
    {
        /// <summary>
        /// Sort by age group
        /// </summary>
        AgeGroup,

        /// <summary>
        /// Sort by author
        /// </summary>
        Author,

        /// <summary>
        /// Sort by contributor
        /// </summary>
        Contributor,

        /// <summary>
        /// Sort by ISBN
        /// </summary>
        ISBN,

        /// <summary>
        /// Sort by price
        /// </summary>
        Price,

        /// <summary>
        /// Sort by publisher
        /// </summary>
        Publisher,

        /// <summary>
        /// Sort by title
        /// </summary>
        Title
    }

    /// <summary>
    /// New York Times Best Seller History API
    /// </summary>
    /// <remarks>
    /// http://developer.nytimes.com/docs/best_sellers_api#h3-history
    /// <list type="table">
    /// <listheader>
    /// <term>Application Resource Key</term>
    /// <description>Key to add to application resources</description>
    /// </listheader>
    /// <item>
    /// <term>
    /// NYTBestSellersKey
    /// </term>
    /// <description>Bestsellers API Key</description>
    /// </item>
    /// <item>
    /// <term>
    /// BestsellersQPS
    /// </term>
    /// <description>Rate Limiting (default 8 QPS)</description>
    /// </item>
    /// </list>
    /// </remarks>
    public class BestsellerHistory : INotifyPropertyChanged
    {
        #region Fields
        /// <summary>
        /// the key for the BestSellers API in the app resources
        /// </summary>
        private const string NYTBestSellersKey = "NYTBestSellersKey";

        /// <summary>
        /// 8 QPS Rate Limiting
        /// </summary>
        private static RateLimiting limiting = new RateLimiting("Bestsellers", 8);

        /// <summary>
        /// the API key
        /// </summary>
        private string apiKey;

        /// <summary>
        /// the title to search for
        /// </summary>
        private string title;

        /// <summary>
        /// the ISBN to search for
        /// </summary>
        private string isbn;
        
        /// <summary>
        /// the copyright message
        /// </summary>
        private string copyright;

        /// <summary>
        /// the sort order
        /// </summary>
        private BestsellerHistorySort sort;

        /// <summary>
        /// the web client
        /// </summary>
        private WebClient client;

        /// <summary>
        /// the list of age group names
        /// </summary>
        private IEnumerable<string> ageGroupList;

        /// <summary>
        /// true if the results are loading, false otherwise
        /// </summary>
        private bool isLoading;

        /// <summary>
        /// the single book results
        /// </summary>
        private Book result;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the BestsellerHistory class.
        /// </summary>
        public BestsellerHistory()
        {
            this.Results = new ObservableCollection<Book>();

            this.RankHistory = new ObservableCollection<BookRank>();

            if (Application.Current.Resources.Contains(NYTBestSellersKey))
            {
                this.ApiKey = Application.Current.Resources[NYTBestSellersKey] as string;
            }

            this.Sort = BestsellerHistorySort.Title;

            if (SampleData.IsInDesignTool)
            {
                var xml = NYTimes.Core.SampleData.GetSampleData("BestsellerHistory.xml");

                if (!string.IsNullOrEmpty(xml))
                {
                    this.ParseXml(xml);
                }
            }
        }
        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Property changed event
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Properties

        /// <summary>
        /// Gets a value indicating whether the history results are loading
        /// </summary>
        public bool IsLoading
        {
            get
            {
                return this.isLoading;
            }

            private set
            {
                if (this.isLoading != value)
                {
                    this.isLoading = value;

                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("IsLoading"));
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the API Key
        /// </summary>
        [Description("The New York Times Best Seller API Key")]
        public string ApiKey
        {
            get
            {
                return this.apiKey;
            }

            set
            {
                if (this.apiKey != value)
                {
                    this.apiKey = value;

                    this.GetHistory();
                }
            }
        }

        /// <summary>
        /// Gets or sets the age group
        /// </summary>
        [Description("The age group for the best sellers.  See the AgeGroupList for a list of values.")]
        public string AgeGroup { get; set; }

        /// <summary>
        /// Gets the list of Age Group values
        /// </summary>
        public IEnumerable<string> AgeGroupList
        {
            get
            {
                if (this.ageGroupList == null)
                {
                    if (string.IsNullOrEmpty(this.ApiKey))
                    {
                        return this.ageGroupList;
                    }

                    var ageGroupListRequest = new WebClient();

                    ageGroupListRequest.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.OnAgeGroupListDownloaded);

                    var uriString = string.Format(CultureInfo.InvariantCulture, "http://api.nytimes.com/svc/books/v2/lists/age-groups.xml?api-key={0}", this.ApiKey);

                    var uri = new Uri(uriString);

                    limiting.MakeWebRequest(ageGroupListRequest, uri);
                }

                return this.ageGroupList;
            }

            private set
            {
                if (this.ageGroupList != value)
                {
                    this.ageGroupList = value;

                    this.PropertyChanged(this, new PropertyChangedEventArgs("AgeGroupList"));
                }
            }
        }

        /// <summary>
        /// Gets or sets the author
        /// </summary>
        [Description("The author of the best seller. The author field does not include additional contributors")]
        public string Author { get; set; }

        /// <summary>
        /// Gets or sets the contributor
        /// </summary>
        [Description("The author of the best seller, as well as other contributors such as the illustrator (to search or sort by author name only, use author instead).")]
        public string Contributor { get; set; }

        /// <summary>
        /// Gets or sets the ISBN
        /// </summary>
        [Description("International Standard Book Number, 10 or 13 digits")]
        public string ISBN
        {
            get
            {
                return this.isbn;
            }

            set
            {
                if (this.isbn != value)
                {
                    this.isbn = value;
                }

                this.GetHistory();
            }
        }

        /// <summary>
        /// Gets or sets the price
        /// </summary>
        [Description("The publisher's list price of the best seller, including decimal point.")]
        public double Price { get; set; }

        /// <summary>
        /// Gets or sets the publisher
        /// </summary>
        [Description("The standardized name of the publisher")]
        public string Publisher { get; set; }

        /// <summary>
        /// Gets or sets the title
        /// </summary>
        [Description("The title of the best seller.  When searching, you can specify a portion of a title or a full title.")]
        public string Title
        {
            get
            {
                return this.title;
            }

            set
            {
                if (this.title != value)
                {
                    this.title = value;

                    this.GetHistory();
                }
            }
        }

        /// <summary>
        /// Gets or sets the sort order
        /// </summary>
        [Description("The sort order for the results.  Default is Title")]
        public BestsellerHistorySort Sort 
        {
            get
            {
                return this.sort;
            }

            set
            {
                if (this.sort != value)
                {
                    this.sort = value;

                    this.GetHistory();
                }
            }
        }

        /// <summary>
        /// Gets the history
        /// </summary>
        public ObservableCollection<Book> Results { get; private set; }

        /// <summary>
        /// Gets the rank history for the single result
        /// </summary>
        public ObservableCollection<BookRank> RankHistory { get; private set; }

        /// <summary>
        /// Gets the single results when returning a specific book
        /// </summary>
        public Book Result
        {
            get
            {
                return this.result;
            }

            private set
            {
                if (this.result != value)
                {
                    this.result = value;

                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("Result"));
                    }
                }
            }
        }

        /// <summary>
        /// Gets the copyright message
        /// </summary>
        public string Copyright
        {
            get
            {
                return this.copyright;
            }

            private set
            {
                if (this.copyright != value)
                {
                    this.copyright = value;

                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs("Copyright"));
                    }
                }
            }
        }

        #endregion

        #region Implementation
        /// <summary>
        /// Get the history of a book
        /// </summary>
        private void GetHistory()
        {
            if (SampleData.IsInDesignTool)
            {
                return;
            }

            this.Results.Clear();

            if (string.IsNullOrEmpty(this.ApiKey))
            {
                return;
            }

            if (string.IsNullOrEmpty(this.Title) && string.IsNullOrEmpty(this.ISBN))
            {
                return;
            }

            if (this.client != null)
            {
                this.client.CancelAsync();
            }

            this.IsLoading = true;

            this.client = new WebClient();

            var address = this.GenerateUri();

            System.Diagnostics.Debug.WriteLine(address);

            this.client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.OnHistoryReturned);

            limiting.MakeWebRequest(this.client, address);
        }

        /// <summary>
        /// Generate the Uri for the web service call
        /// </summary>
        /// <returns>the REST URI</returns>
        private Uri GenerateUri()
        {
            string uriString = string.Format(System.Globalization.CultureInfo.InvariantCulture, "http://api.nytimes.com/svc/books/{0}/lists/best-sellers/history.xml?api-key={1}", "v2", this.ApiKey);

            var builder = new StringBuilder(uriString);

            if (!string.IsNullOrEmpty(this.Title))
            {
                builder.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "&title={0}", this.Title);
            }

            if (!string.IsNullOrEmpty(this.ISBN))
            {
                builder.AppendFormat(System.Globalization.CultureInfo.InvariantCulture, "&isbn={0}", this.ISBN);
            }

            string sortBy = string.Empty;

            switch (this.Sort)
            {
                case BestsellerHistorySort.AgeGroup:
                    sortBy = "age-group";
                    break;

                case BestsellerHistorySort.Author:
                    sortBy = "author";
                    break;

                case BestsellerHistorySort.Contributor:
                    sortBy = "contributor";
                    break;

                case BestsellerHistorySort.ISBN:
                    sortBy = "isbn";
                    break;

                case BestsellerHistorySort.Price:
                    sortBy = "price";
                    break;

                case BestsellerHistorySort.Publisher:
                    sortBy = "publisher";
                    break;

                case BestsellerHistorySort.Title:
                    sortBy = "title";
                    break;
            }

            if (!string.IsNullOrEmpty(sortBy))
            {
                builder.AppendFormat(CultureInfo.InvariantCulture, "&sort-by={0}", sortBy);
            }

            var address = new Uri(builder.ToString(), UriKind.Absolute);
            return address;
        }

        /// <summary>
        /// Age group list has been downloaded
        /// </summary>
        /// <param name="sender">the web client</param>
        /// <param name="e">the download string completed event arguments</param>
        private void OnAgeGroupListDownloaded(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                System.Diagnostics.Debug.WriteLine("Error downloading BestsellerHistory age group list: {0}", e.Error.Message);

                this.PropertyChanged(this, new PropertyChangedEventArgs("AgeGroupList"));

                return;
            }

            var doc = XDocument.Parse(e.Result);

            var ageGroups = from element in doc.Descendants("age_group")
                            select element.Value;

            this.AgeGroupList = ageGroups;
        }

        /// <summary>
        /// web client returned the history
        /// </summary>
        /// <param name="sender">the web client</param>
        /// <param name="e">the download string completed event arguments</param>
        private void OnHistoryReturned(object sender, DownloadStringCompletedEventArgs e)
        {
            this.client = null;

            if (e.Error != null)
            {
                if (!e.Cancelled)
                {
                    System.Diagnostics.Debug.WriteLine(e.Error.Message);

                    this.IsLoading = false;
                }

                return;
            }

            var xml = e.Result;

            this.ParseXml(xml);

            this.IsLoading = false;
        }

        /// <summary>
        /// Parse the XML returned from the BestSellerHistory service
        /// </summary>
        /// <param name="xml">the Bestseller history XML</param>
        private void ParseXml(string xml)
        {
            var doc = XDocument.Parse(xml);

            var resultSet = doc.Element("result_set");

            this.Copyright = resultSet.Element("copyright").Value;

            var results = resultSet.Element("results");

            var rank_history = doc.Descendants("ranks_history").FirstOrDefault();

            if (rank_history != null)
            {
                var history = from book in results.Elements("book")
                              select new Book()
                              {
                                  Details = new BookDetail()
                                  {
                                      AgeGroup = book.Element("age_group").Value,
                                      Author = book.Element("author").Value,
                                      Contributor = book.Element("contributor").Value,
                                      ContributorNote = book.Element("contributor_note").Value,
                                      Description = book.Element("description").Value,
                                      Price = double.Parse(book.Element("price").Value, CultureInfo.InvariantCulture),
                                      Publisher = book.Element("publisher").Value,
                                      Title = book.Element("title").Value
                                  },
                                  ISBNs = from isbn in book.Element("isbns").Elements("isbn")
                                          select new ISBN()
                                          {
                                              ISBN10 = isbn.Element("isbn10").Value,
                                              ISBN13 = isbn.Element("isbn13").Value
                                          },
                                  RanksHistory = book.Element("ranks_history") == null ?
                                    null :
                                    from rank in book.Element("ranks_history").Elements("rank")
                                    select new BookRank()
                                    {
                                        Rank = int.Parse(rank.Element("rank").Value, CultureInfo.InvariantCulture),
                                        ListName = rank.Element("list_name").Value,
                                        BestsellersDate = DateTime.Parse(rank.Element("bestsellers_date").Value, CultureInfo.InvariantCulture),
                                        PublishedDate = DateTime.Parse(rank.Element("published_date").Value, CultureInfo.InvariantCulture),
                                        WeeksOnList = int.Parse(rank.Element("weeks_on_list").Value, CultureInfo.InvariantCulture),
                                        RankLastWeek = int.Parse(rank.Element("rank_last_week").Value, CultureInfo.InvariantCulture),
                                        Asterisk = int.Parse(rank.Element("asterisk").Value, CultureInfo.InvariantCulture) == 1,
                                        Dagger = int.Parse(rank.Element("dagger").Value, CultureInfo.InvariantCulture) == 1,
                                    },
                                  Reviews = from review in book.Element("reviews").Elements("review")
                                            select new Review()
                                            {
                                                ArticleChapter = string.IsNullOrEmpty(review.Element("article_chapter_link").Value) ? null : new Uri(review.Element("article_chapter_link").Value, UriKind.Absolute),
                                                BookReview = string.IsNullOrEmpty(review.Element("book_review_link").Value) ? null : new Uri(review.Element("book_review_link").Value, UriKind.Absolute),
                                                FirstChapter = string.IsNullOrEmpty(review.Element("first_chapter_link").Value) ? null : new Uri(review.Element("first_chapter_link").Value, UriKind.Absolute),
                                                SundayReview = string.IsNullOrEmpty(review.Element("sunday_review_link").Value) ? null : new Uri(review.Element("sunday_review_link").Value, UriKind.Absolute),
                                            }
                              };

                var first = history.FirstOrDefault();

                this.Result = first;

                if (first != null)
                {
                    this.RankHistory.Clear();

                    if (first.RanksHistory != null)
                    {
                        foreach (var rank in first.RanksHistory)
                        {
                            this.RankHistory.Add(rank);
                        }
                    }
                }

                foreach (var book in history)
                {
                    this.Results.Add(book);
                }
            }
        }
        #endregion
    }
}
